package com.fkdx.common.sequence;

import com.fkdx.common.enums.sys.GroupException;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.sql.*;
import java.util.HashMap;
import java.util.Map;

/**
 *
 * @author liujia
 */
@Log4j2
@Component
public class Sequence {

    private final static String SELECT_SQL = "SELECT id FROM sequence_value WHERE name = ?";
    private final static String INSERT_SQL = "INSERT INTO sequence_value (id, name) VALUES (?, ?)";
    private final static String UPDATE_SQL = "UPDATE sequence_value SET id = ?  WHERE name = ? AND id = ?";

    private Map<String,Step> stepMap = new HashMap<>();

    private int blockSize = 50;
    private long startValue = 0;

    @Resource
    private DataSource dataSource;

    public synchronized long get(String sequenceName) {
        Step step = stepMap.get(sequenceName);
        if(step == null) {
            step = new Step(startValue,startValue + blockSize);
            stepMap.put(sequenceName, step);
        } else {
            if (step.currentValue < step.endValue) {
                return step.incrementAndGet();
            }
        }
        for (int i = 0; i < blockSize; i++) {
            if (getNextBlock(sequenceName,step)) {
                return step.incrementAndGet();
            }
        }
        throw new GroupException("No more value.");
    }

    private boolean getNextBlock(String sequenceName, Step step) {
        Long value = getPersistenceValue(sequenceName);
        if (value == null) {
            try {
                value = newPersistenceValue(sequenceName);
            } catch (Exception e) {
                log.error("newPersistenceValue error!");
                value = getPersistenceValue(sequenceName);
            }
        }
        boolean b = saveValue(value, sequenceName) == 1;
        if (b) {
            step.setCurrentValue(value);
            step.setEndValue(value+blockSize);
        }
        return b;
    }

    private int saveValue(long value, String sequenceName) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(UPDATE_SQL);
            statement.setLong(1, value + blockSize);
            statement.setString(2, sequenceName);
            statement.setLong(3, value);
            return statement.executeUpdate();
        } catch (Exception e) {
            log.error("newPersistenceValue error!", e);
            throw new GroupException("newPersistenceValue error!", e);
        } finally {
            closeStatement(statement);
            closeConnection(connection);
        }
    }

    private Long getPersistenceValue(String sequenceName) {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(SELECT_SQL);
            statement.setString(1, sequenceName);
            resultSet = statement.executeQuery();
            if (resultSet.next()) {
                return resultSet.getLong("id");
            }
        } catch (Exception e) {
            log.error("getPersistenceValue error!", e);
            throw new GroupException("getPersistenceValue error!", e);
        } finally {
            closeResultSet(resultSet);
            closeStatement(statement);
            closeConnection(connection);
        }
        return null;
    }

    private Long newPersistenceValue(String sequenceName) {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = dataSource.getConnection();
            statement = connection.prepareStatement(INSERT_SQL);
            statement.setLong(1, startValue);
            statement.setString(2, sequenceName);
            statement.executeUpdate();
        } catch (Exception e) {
            log.error("newPersistenceValue error!", e);
            throw new GroupException("newPersistenceValue error!", e);
        } finally {
            closeStatement(statement);
            closeConnection(connection);
        }
        return startValue;
    }

    private void closeResultSet(ResultSet resultSet) {
        if (resultSet != null) {
            try {
                resultSet.close();
            } catch (SQLException e) {
                log.error("close resultset error!", e);
            }
        }
    }
    private void closeStatement(Statement statement) {
        if (statement != null) {
            try {
                statement.close();
            } catch (SQLException e) {
                log.error("close statement error!", e);
            }
        }
    }

    private void closeConnection(Connection connection) {
        if (connection != null) {
            try {
                connection.close();
            } catch (SQLException e) {
                log.error("close connection error!", e);
            }
        }
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public void setBlockSize(int blockSize) {
        this.blockSize = blockSize;
    }

    public void setStartValue(long startValue) {
        this.startValue = startValue;
    }

    static class Step {
        private long currentValue;
        private long endValue;

        Step(long currentValue, long endValue) {
            this.currentValue = currentValue;
            this.endValue = endValue;
        }

        public void setCurrentValue(long currentValue) {
            this.currentValue = currentValue;
        }

        void setEndValue(long endValue) {
            this.endValue = endValue;
        }

         long incrementAndGet() {
            return ++currentValue;
        }
    }

}
